package goqc

import (
	"fmt"
	"math"
	"os"
	"rand"
	"reflect"
	"strings"
	"time"
)

type Gen func() interface{}

func choice() bool {
	return rand.Int()&1 == 0
}

type intSpec struct {
	Min, Max, GenMax int64
	Fn               func(int64) interface{}
}

var int32Spec = intSpec{
	Min:    math.MinInt32,
	Max:    math.MaxInt32,
	GenMax: 200,
	Fn: func(x int64) interface{} {
		return int32(x)
	},
}
var int64Spec = intSpec{
	Min:    math.MinInt64,
	Max:    math.MaxInt64,
	GenMax: 200,
	Fn: func(x int64) interface{} {
		return x
	},
}

var int16Spec = intSpec{
	Min:    math.MinInt16,
	Max:    math.MaxInt16,
	GenMax: 200,
	Fn: func(x int64) interface{} {
		return int16(x)
	},
}

var int8Spec = intSpec{
	Min:    math.MinInt8,
	Max:    math.MaxInt8,
	GenMax: 128,
	Fn: func(x int64) interface{} {
		return int8(x)
	},
}

var intNSpec = intSpec{
	Min:    math.MinInt32,
	Max:    math.MaxInt32,
	GenMax: 200,
	Fn: func(x int64) interface{} {
		return int(x)
	},
}

func (is intSpec) GetGen() Gen {
	min, max := is.Min, is.Max
	known := []int64{min, max, -1, 0, 1, max, min, -1, 0, 1}
	return func() interface{} {
		if len(known) > 0 {
			i := rand.Intn(len(known))
			v := known[i]
			known[i], known[0] = known[0], known[i]
			known = known[1:]
			return is.Fn(v)
		}
		v := rand.Int63n(is.GenMax)
		if choice() {
			v = -v
		}
		return is.Fn(v)
	}
}

type GenFn func() Gen

func (gf GenFn) GetGen() Gen {
	return gf()
}

var mkUint32Gen GenFn = func() Gen {
	known := []uint32{math.MaxUint32, 0, 1}
	return func() interface{} {
		if len(known) > 0 {
			v := known[0]
			known = known[1:]
			return v
		}
		return uint32(rand.Int63n(math.MaxUint32))
	}
}

var mkByteGen GenFn = func() Gen {
	known := []byte{math.MaxUint8, 0, 1}
	return func() interface{} {
		if len(known) > 0 {
			v := known[0]
			known = known[1:]
			return v
		}
		return byte(rand.Intn(255))
	}
}

var mkBoolGen GenFn = func() Gen {
	return func() interface{} {
		return choice()
	}
}

func mkSliceGen(t reflect.Type, mkeltgen func() Gen) Gen {
	return func() interface{} {
		l := rand.Intn(20)
		sl := reflect.MakeSlice(t, 0, l)
		eltgen := mkeltgen()
		for i := 0; i < l; i++ {
			sl = reflect.Append(sl, reflect.ValueOf(eltgen()))
		}
		return sl.Interface()
	}
}

func mkArrayGen(t reflect.Type, mkeltgen func() Gen) Gen {
	return func() interface{} {
		l := t.Len()
		ar := reflect.New(t).Elem()
		eltgen := mkeltgen()
		for i := 0; i < l; i++ {
			ar.Index(i).Set(reflect.ValueOf(eltgen()))
		}
		return ar.Interface()
	}
}

func randomstring(runes []int, l int, b []int) []int {
	b = b[0:0]
	for i := 0; i < l; i++ {
		b = append(b, runes[rand.Intn(len(runes))])
	}
	return b
}

var mkStringGen GenFn = func() Gen {
	runes := []int("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef \t$tuvwzha,zmX&1;jP'☃(){}\\[]^$*")
	known := []string{""}
	buf := make([]int, 0, 32)
	return func() interface{} {
		if len(known) > 0 {
			v := known[0]
			known = known[1:]
			return v
		}
		buf = randomstring(runes, rand.Intn(18)+2, buf)
		return string(buf)
	}
}

var mkBytesGen GenFn = func() Gen {
	return func() interface{} {
		n := rand.Intn(20)
		buf := make([]byte, 0, n)
		for i := 0; i < n; i++ {
			buf = append(buf, byte(rand.Intn(255)))
		}
		return buf
	}
}

var (
	intType    = reflect.TypeOf(int(0))
	boolType   = reflect.TypeOf(false)
	stringType = reflect.TypeOf("")
)

var defaultGenMap = map[reflect.Type]GenSource{
	reflect.TypeOf(byte(0)):   mkByteGen,
	reflect.TypeOf([]byte{}):  mkBytesGen,
	intType:                   intNSpec,
	boolType:                  mkBoolGen,
	stringType:                mkStringGen,
	reflect.TypeOf(int64(0)):  int64Spec,
	reflect.TypeOf(int32(0)):  int32Spec,
	reflect.TypeOf(int16(0)):  int16Spec,
	reflect.TypeOf(uint32(0)): mkUint32Gen,
}

type Result struct {
	Vals []interface{}
}

func (err *Result) String() string {
	var sv []string
	for _, v := range err.Vals {
		sv = append(sv, fmt.Sprintf("%#v", v))
	}
	return "Counter-example: (" + strings.Join(sv, ", ") + ")"
}

const chatty = false

type Config struct {
	Cases int
	Gens  map[reflect.Type]GenSource
}

type GenSource interface {
	GetGen() Gen
}

var genSourceType = reflect.TypeOf((*GenSource)(nil)).Elem()

func (c *Config) GetGen(t reflect.Type) Gen {
	if gg := c.Gens[t]; gg != nil {
		return gg.GetGen()
	}
	if gg := defaultGenMap[t]; gg != nil {
		return gg.GetGen()
	}
	if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
		if c.GetGen(t.Elem()) == nil {
			return nil
		}
		mkeltgen := func() Gen {
			return c.GetGen(t.Elem())
		}
		if t.Kind() == reflect.Array {
			return mkArrayGen(t, mkeltgen)
		}
		return mkSliceGen(t, mkeltgen)
	}
	if t.Implements(genSourceType) {
		return reflect.Zero(t).Interface().(GenSource).GetGen()
	}
	return nil
}

func defaultConfig() *Config {
	return &Config{
		Cases: 1000,
	}
}

func Check(fn interface{}, config *Config) (*Result, os.Error) {
	if config == nil {
		config = defaultConfig()
	}
	fnv := reflect.ValueOf(fn)
	fnt := fnv.Type()
	if fnt.NumOut() != 1 || fnt.Out(0).Kind() != reflect.Bool {
		return nil, fmt.Errorf("Proposition function must return a bool")
	}
	var gens []Gen
	for i := 0; i < fnt.NumIn(); i++ {
		it := fnt.In(i)
		gen := config.GetGen(it)
		if gen == nil {
			return nil, fmt.Errorf("Can't create generator for type: %v", it)
		}
		gens = append(gens, gen)
	}
	vals := make([]reflect.Value, len(gens))
	t0 := time.Nanoseconds()
	Cases := config.Cases
	if Cases == 0 {
		Cases = defaultConfig().Cases
	}
	for i := 0; i < Cases; i++ {
		for i := range vals {
			vals[i] = reflect.ValueOf(gens[i]())
		}
		r := fnv.Call(vals)
		if !r[0].Bool() {
			var inters []interface{}
			for _, v := range vals {
				inters = append(inters, v.Interface())
			}
			return &Result{inters}, nil
		}
	}
	if chatty {
		fmt.Printf("%v cases per second.\n", float64(Cases)/(float64(time.Nanoseconds()-t0)/1e9))
	}
	return nil, nil
}
